package sf.database.dialect.nosql;

import sf.common.exception.SmallOrmException;
import sf.core.DBField;
import sf.database.dbinfo.Feature;
import sf.database.dialect.DBDialect;
import sf.database.dialect.DBProperty;
import sf.database.dialect.Keywords;
import sf.database.meta.CascadeConfig;
import sf.database.meta.ColumnMapping;
import sf.database.meta.TableMapping;
import sf.database.support.DBMS;
import sf.database.util.DBUtils;
import sf.database.util.SQLUtils;
import sf.jooq.JooqSupportDatabase;
import sf.querydsl.QueryDSLSupportDatabase;
import sf.tools.StringUtils;
import sf.tools.SystemUtils;

import javax.persistence.Index;
import javax.persistence.Table;
import java.sql.Connection;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * clickhouse
 */
public class ClickHouseDialect extends DBDialect {

    protected static final String META_ENGINE = "clickhouse-engine";

    @Override
    public String getName() {
        return DBMS.clickhouse.name();
    }

    @Override
    public int getNumber() {
        return DBMS.clickhouse.getNumber();
    }

    @Override
    public boolean isNosql() {
        return true;
    }

    public ClickHouseDialect() {
        this.keywords.addAll(Keywords.CLICKHOUSE);
        // 在MYSQL中 ||是逻辑运算符
        features.addAll(Arrays.asList(Feature.DBNAME_AS_SCHEMA, Feature.SUPPORT_INLINE_COMMENT, Feature.ALTER_FOR_EACH_COLUMN, Feature.NOT_FETCH_NEXT_AUTOINCREAMENTD, Feature.SUPPORT_LIMIT, Feature.COLUMN_DEF_ALLOW_NULL));
        setProperty(DBProperty.ADD_COLUMN, "ADD");
        setProperty(DBProperty.MODIFY_COLUMN, "MODIFY");
        setProperty(DBProperty.DROP_COLUMN, "DROP COLUMN");
        setProperty(DBProperty.CHECK_SQL, "select 1");
        setProperty(DBProperty.SELECT_EXPRESSION, "select %s");
        setProperty(DBProperty.WRAP_FOR_KEYWORD, "``");
        setProperty(DBProperty.INDEX_LENGTH_LIMIT, "767");
        setProperty(DBProperty.INDEX_LENGTH_LIMIT_FIX, "255");
        setProperty(DBProperty.DROP_INDEX_TABLE_PATTERN, "%1$s ON %2$s");
        setProperty(DBProperty.NVL_FUNCTION, "ifnull(%1$s,%2$s)");
    }

    @Override
    public void evalFieldDefinitionLine(ColumnMapping mf, StringBuilder type) {
        String defaultValue = getColumnDefaultValue(mf);
        type.append(evalFieldType(mf));
        //不支持
//          if (mf.getColumn() != null && mf.getColumn().unique()) {
//              type.append(" UNIQUE ");
//          }
        if ((mf.getColumn() != null && !mf.getColumn().nullable()) || mf.isPk()) {
            type.append(" NOT NULL");
        }
        if (StringUtils.isNotBlank(defaultValue)) {
            type.append(" DEFAULT ").append(defaultValue);
        }
    }

    @Override
    public String evalFieldType(ColumnMapping mf) {
        String type = "";
        int length = getColumnLength(mf);
        int scale = getColumnScale(mf);
        int precision = getColumnPrecision(mf);
        switch (mf.getSqlType()) {
            case Types.VARCHAR: {
                type = "String";
                break;
            }
            case Types.TINYINT: {
                type = "Int8";
                break;
            }
            case Types.SMALLINT: {
                type = "Int16";
                break;
            }
            case Types.INTEGER: {
                type = "Int32";
                break;
            }
            case Types.BIGINT: {
                type = "Int64";
                break;
            }
            case Types.FLOAT: {
                type = "Float32";
                break;
            }
            case Types.DOUBLE:
                type = "Float64";
                break;
            case Types.BOOLEAN: {
                type = "UInt8";
                break;
            }
            case Types.NUMERIC:
            case Types.DECIMAL: {
                // BigDecimal
                if (scale > 0 && precision > 0) {
                    type = "DECIMAL(" + precision + "," + scale + ")";
                } else {
                    throw new UnsupportedOperationException("Unsupport type: " + mf.getRawColumnName());
                }
                break;
            }
            case Types.DATE:
                type = "Date32";
                break;
            case Types.TIME:
                type = "UInt16";
                break;
            case Types.TIMESTAMP:
                type = "DateTime64(3)";
                break;
            case Types.BLOB:
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
                type = "Array(Int8)";
                break;
            case Types.CLOB:
            case Types.LONGVARCHAR:
            case Types.LONGNVARCHAR:
                type = "String";
                break;
            default:
                throw new UnsupportedOperationException("Unsupport type: " + mf.getRawColumnName());
        }
        return type;
    }

    @Override
    public boolean createEntity(Connection conn, TableMapping en) {
        if (en.getTableName() == null) {
            throw new RuntimeException("table name is empty!");
        }
        StringBuilder sb = new StringBuilder("CREATE TABLE " + wrapKeyword(en.getTableName()) + "(");
        // 创建字段
        boolean delimiter = false;
        for (Map.Entry<DBField, ColumnMapping> entry : en.getSchemaMap().entrySet()) {
            ColumnMapping cm = entry.getValue();
            if (cm.getJpaTransient() != null) {
                continue;
            }
            sb.append(delimiter ? "," : "").append(SystemUtils.lineSeparator);
            sb.append(wrapKeyword(cm.getRawColumnName())).append(' ').append(evalFieldDefinition(cm));

            //不支持自增
            if (StringUtils.isNotBlank(cm.getComment())) {
                sb.append(" COMMENT '").append(cm.getComment()).append("'");
            }
            delimiter = true;
        }
        // 创建主键
        List<ColumnMapping> pks = en.getPkFields();
        if (!pks.isEmpty()) {
            sb.append(',').append(SystemUtils.lineSeparator);
            sb.append("PRIMARY KEY (");
            boolean f = false;
            for (ColumnMapping pk : pks) {
                sb.append(f ? "," : "").append(wrapKeyword(pk.getRawColumnName()));
                f = true;
            }
            sb.append(") ").append(SystemUtils.lineSeparator);
        }

        // 结束表字段设置
        sb.append(')');
        // 设置特殊引擎
        if (en.hasMeta(META_ENGINE)) {
            sb.append(" ENGINE=").append(en.getMeta(META_ENGINE));
        } else {
            sb.append(" ENGINE=MergeTree()");//默认引擎
        }

        // 表名注释
        if (StringUtils.isNotBlank(en.getComment())) {
            sb.append(" COMMENT '").append(en.getComment()).append("'");
        }

        List<String> sqls = new ArrayList<>();
        // 执行创建语句
        sqls.add(sb.toString());

        // 创建索引
        sqls.addAll(createIndexSql(en));

        //创建约束
//        sqls.addAll(createUniqueSql(en));

        execute(sqls, conn);

        // 创建关联表
        createRelation(conn, en);
        return true;
    }

    protected List<String> createRelation(Connection conn, ColumnMapping en) {
        List<String> sqls = new ArrayList<>();
        List<String> columns = new ArrayList<>();
        CascadeConfig cc = en.getCascadeConfig();
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        String middleTableName = cc.getMiddleTableName();
        if (StringUtils.isBlank(middleTableName) || existsTables(conn, middleTableName)) {
            return sqls;
        }
        StringBuilder sql = new StringBuilder();
        sql.append("CREATE TABLE ").append(wrapKeyword(cc.getMiddleTableName())).append("(");
        boolean f = false;
        for (Map.Entry<String, ColumnMapping> entry : cc.getMiddleTableColumns().entrySet()) {
            String key = entry.getKey();
            key = wrapKeyword(key);
            columns.add(key);
            sql.append(f ? "," : "").append(key).append(" ").append(evalFieldType(entry.getValue())).append(" not null ");
            f = true;
        }
        sql.append(",").append(" PRIMARY KEY (").append(StringUtils.join(columns, ",")).append(")");
        sql.append(")");
        sql.append(" ENGINE=MergeTree()");//默认引擎
        sqls.add(sql.toString());
        return sqls;
    }

    public List<String> createIndexSql(TableMapping tm) {
        List<String> sqls = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        Table table = tm.getTable();
        if (table == null) {
            return sqls;
        }
        Index[] indexs = table.indexes();
        for (int i = 0; i < indexs.length; i++) {
            Index index = indexs[i];
            sb.append("ALTER TABLE " + wrapKeyword(tm.getTableName()) + " ADD INDEX ");

            if (index.name().isEmpty()) {
                sb.append("idx_").append(tm.getTableName()).append("_").
                        append(index.columnList().replace(" ", "").replace(",", "_"));
            } else {
                sb.append(index.name());
            }
            sb.append("(");
            String[] columns = StringUtils.split(index.columnList(), ",");
            boolean f = false;
            for (int j = 0; j < columns.length; j++) {
                String columnAll = columns[j];
                columnAll = columnAll.trim();
                String[] splits = StringUtils.split(columnAll, " ");
                String column = splits[0];
                splits[0] = "";
                //判断是否是数据库字段
                ColumnMapping temp = SQLUtils.getColumnByDBName(tm, column);
                if (temp == null) {
                    //不为数据库字段
                    //如果为java字段名称
                    temp = tm.getJavaFieldColumnMapping(column);
                }
                if (temp != null) {
                    column = temp.getRawColumnName();
                } else {
                    throw new SmallOrmException(tm.getThisType().getName() + " 该字段" + column + " 不是数据库字段名,也不是java字段名!");
                }
                sb.append(f ? "," : "").append(wrapKeyword(column)).append(StringUtils.join(splits, " "));
                f = true;
            }
            sb.append(") TYPE set(0) GRANULARITY 8192");//注意TYPE有多种类型,GRANULARITY 取得是默认值
            sqls.add(sb.toString());
            sb.delete(0, sb.length());
        }
        return sqls;
    }

    @Override
    public String uniqueSql(String tableName, String name, String[] columnNames) {
        throw new UnsupportedOperationException("not support");
    }

    @Override
    protected String escapeSqlValue(String value) {
        return StringUtils.replace(value, "'", "\\'");
    }

    @Override
    public String sqlTableDrop(String table) {
        return String.format("DROP TABLE IF EXISTS %s;", wrapKeyword(table));
    }

    @Override
    public String sqlTableRename(String oldName, String newName) {
        return String.format("RENMAE TABLE %s TO %s;", wrapKeyword(oldName), wrapKeyword(newName));
    }

    @Override
    public String sqlColumnRename(String table, String oldColumnName, String newColumnName) {
        return String.format("ALTER TABLE %s RENAME COLUMN %s TO %s", wrapKeyword(table), wrapKeyword(oldColumnName), wrapKeyword(newColumnName));
    }

    @Override
    public String sqlColumnAdd(String table, String column_definition, String column_position) {
        String sql = String.format("ALTER TABLE %s ADD %s", wrapKeyword(table), column_definition);
        if (supportsColumnPosition() && column_position != null) {
            sql = sql + " " + column_position;
        }
        return sql + ";";
    }

    @Override
    public String sqlColumnModify(String table, String column_definition, String column_position) {
        String sql = String.format("ALTER TABLE %s MODIFY %s", wrapKeyword(table), column_definition);
        if (supportsColumnPosition() && column_position != null) {
            sql = sql + " " + column_position;
        }
        return sql + ";";
    }

    @Override
    public String sqlColumnDrop(String table, String column) {
        return String.format("ALTER TABLE %s DROP %s;", wrapKeyword(table), wrapKeyword(column));
    }

    public String sqlColumnClear(String table, String column, String partition_name) {
        return String.format("ALTER TABLE %s CLEAR COLUMN %s IN PARTITION %s", wrapKeyword(table), wrapKeyword(column), wrapKeyword(partition_name));
    }

    public String sqlColumnComment(String table, String column, String comment) {
        return String.format("ALTER TABLE %s COMMENT COLUMN %s '%s'", wrapKeyword(table), wrapKeyword(column), wrapKeyword(comment));
    }

    @Override
    public StringBuilder sqlPageList(StringBuilder sql, long offset, int limit) {
        if (offset > 0) {
            return sql.append(" limit ").append(offset).append(",").append(limit);
        } else {
            return sql.append(" limit ").append(limit);
        }
    }

    @Override
    public boolean supportsColumnPosition() {
        return true;
    }

    @Override
    public String getHibernateDialect() {
        return "org.hibernate.dialect.MySQLDialect";
    }

    @Override
    public String getCatalog(String catalog) {
        return catalog;
    }

    @Override
    public String getQueryDslDialect() {
        return QueryDSLSupportDatabase.MySQL;
    }

    @Override
    public String getJooqDialect() {
        return JooqSupportDatabase.MYSQL;
    }
}